using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SolarWinds.Cameras;
using SolarWinds.Helpers;

namespace SolarWinds.Particles
{
    public enum ParticleEmitterType
    {
        Omni,
        Directional,
        Line,
        Rectangle,
        Circle
    }

    public abstract class ParticleEmitter
    {
        #region Fields and Properties

        private float mSumElapsed;

        // Note how we order active and inactive particles inside the array:
        // Active Particle are at the beginning of the array and inactive 
        // Particles start from the end. We could index them like this:
        //  ________________________________________________________________________________________________
        // | active particle list                          | inactive particle list                         |
        // | 0 | 1 | 2 | 3 | .... | mActiveParticleCount-1 | mActiveParticleCount | ... | mMaxParticleCount |
        // |_______________________________________________|________________________________________________|
        //
        // We must be able to perform several actions on this atructure:
        // 
        // 1) Access all active particles. This can be simply achieved by 
        //    interating through the particles from 0 to mActiveParticleCount-1
        //
        // 2) Add a new active particle. This action is needed each time the 
        //    emitter generates a new particle. We achieve it by increasing the 
        //    variable mActiveParticleCount by one. By doing this the active 
        //    particle will grow by one particle and the inactive list will 
        //    shrink respectively. The new particle has the index 
        //    (mActiveParticleCount - 1).
        // 
        // 3) Remove an active particle when its lifespan has elapsed - the 
        //    particle dies. To remove a particle from the active list, we 
        //    first move it to the end of the active particle list and then
        //    modify the sizes of the list. Moving the particle to the end of 
        //    the list can be achieved by swapping the particle to be removed 
        //    with the last particle in the active particle list. We then 
        //    decrease mActiveParticleCount by one tio remove the particle from
        //    the active list.

        private Particle[] mParticles;
        public Particle[] Particles
        {
            get { return mParticles; }
        }

        private int mActiveParticleCount;
        public int ActiveParticleCount
        {
            get { return mActiveParticleCount; }
        }
        
        protected readonly ParticleEmitterDescription mDescription;
        public ParticleEmitterDescription Description
        {
            get { return mDescription; }
        }

        private readonly List<IParticleForce> mForces = new List<IParticleForce>();
        public List<IParticleForce> Forces
        {
            get { return mForces; }
        }

        #endregion

        protected ParticleEmitter(ParticleEmitterDescription description)
        {
            mDescription = description;

            InitParticles();
        }

        #region Emit Particles

       
        private void EmitParticle()
        {
            Particle p = ActivateParticle();

            if (p != null)
            {
                // set general particle attributes
                p.Life = 0.0f;
                p.LifeSpan = mDescription.LifeSpan;
                p.Mass = mDescription.Mass;
                p.Twist = (float)RandomHelper.Random.NextDouble();
                p.Size = Vector2.One * 1.5f;
                p.Color = Color.Red;

                InitParticle(ref p);
            }
        }

        /// <summary>
        /// Defines the way a new particle is generated. This method has to overriden in subclass.
        /// </summary>
        /// <param name="p">The newly emitted particle.</param>
        protected abstract void InitParticle(ref Particle p);


        #endregion

        #region Particle Convenience Members

        private void InitParticles()
        {
            //Allocate all the particles - this is only done once
            mParticles = new Particle[mDescription.MaxParticles];
            for (int pi = 0; pi < mParticles.Length; ++pi)
            {
                mParticles[pi] = new Particle();    
            }

            // we start with zero particles
            mActiveParticleCount = 0;

            mSumElapsed = 0.0f;
            mTotalElapsed = 0.0f;
        }

        private Particle ActivateParticle()
        {
            if (mActiveParticleCount == mParticles.Length)
            {
                return null;
            }

            Particle p = mParticles[mActiveParticleCount]; //Take the first inactive particle...

            ++mActiveParticleCount;

            return p;
        }

        private void DeactivateParticle(int index)
        {
            // first check if the particle is an active one
            if (index < mActiveParticleCount)
            {
                // move particle to the end of the active list
                SwapParticles(ref mParticles[index], ref mParticles[mActiveParticleCount - 1]); // TODO chekc if this is working !!!

                // decrease size of active list
                --mActiveParticleCount;
            }
        }

        private static void SwapParticles(ref Particle p1, ref Particle p2)
        {
            Particle tmp = p2;
            p2 = p1;
            p1 = tmp;
        }

        #endregion

        #region Updates

        public void Update(GameTime gameTime)
        {
            UpdateParticles((float)gameTime.ElapsedGameTime.TotalSeconds);

            UpdateBounds();
        }

        readonly List<Vector3> mParticlePositions = new List<Vector3>();

        private void UpdateBounds()
        {
            // Updates
            mParticlePositions.Clear();
            for (int pi = 0; pi < mActiveParticleCount; ++pi)
            {
                Particle p = mParticles[pi];

                mParticlePositions.Add(p.Position);
            }
        }

        float mTotalElapsed;

        public void UpdateParticles(float elapsedTime)
        {
            if (!mDescription.Active)
            {
                return;
            }

            mSumElapsed += elapsedTime;
            mTotalElapsed += elapsedTime;

            // STEP 1: Emit new particles
            if (mDescription.EmitForever ||
                mTotalElapsed < mDescription.EmitTime)
            {
                var newCount = (int)(mSumElapsed * mDescription.EmissonRate);
                if (newCount > 0)
                {
                    mSumElapsed = 0.0f;
                }

                for (int i = 0; i < newCount; ++i)
                {
                    EmitParticle();
                }
            }

            // STEP 2: Update active particles
            for (int pi = 0; pi < mActiveParticleCount; ++pi)
            {
                Particle p = mParticles[pi];

                // update particles liftime
                p.Life += (elapsedTime / p.LifeSpan);

                // check life time of particle
                if (p.Life > 1.0f)
                {
                    // let the particle die
                    DeactivateParticle(pi);
                }
                else
                {
                    // update all assigned forces
                    foreach (IParticleForce force in mForces)
                    {
                        force.Update(elapsedTime, ref p);
                    }

                    // update position
                    p.PreviousPosition = p.Position;
                    p.Position += p.Velocity * elapsedTime;

                    // update size
                    p.Size *= 0.9998f; // TODO get size from texture

                    // uupdate rotation
                    p.Twist += 0.01f;
                }
            }
        }

        #endregion
    }
}